#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/socket.h>

#define PROG_NAME "njubras-daemon"

#define PID_FILE_PATH "/var/run/njubras.pid"

#define SOCKET_PAHT "/tmp/njubras-for-linux.socket"
#define SOCKADDR (struct sockaddr *)

#define COMMAND_LENGTH 80
#define COMMAND_NUM 6

struct cmd_t
{
	char *command;
	int command_len;
};

int server_sockfd;
struct sockaddr_un server_addr;
size_t server_addr_size;

int client_sockfd;
struct sockaddr_un client_addr;
size_t client_addr_size;

void is_root();
void is_only_one();

void handle_signal();
void daemon_exit();

void init_server(void);
void init_command_array(void);
void get_command(void);
int is_command_admitted(void);

struct cmd_t admitted_command[COMMAND_NUM];
char command[COMMAND_LENGTH];


int main()
{
	
	is_root();
	
	is_only_one();

	handle_signal();

	init_command_array();

	init_server();

	while (1)
	{
		get_command();

		if (is_command_admitted())
			system(command);
	}

	exit(EXIT_SUCCESS);
}

void is_root()
{
	uid_t user_id;

	user_id=getuid();

	if (user_id!=0)
	{
		printf("Root privilege needed to run the daemon. Exiting.\n");
		exit(EXIT_FAILURE);
	}
}

void is_only_one()
{
	#define PID_LENGTH 7
	#define PATH_LENGTH 20
	#define BUFFER_LENGTH 50

	pid_t daemon_pid;
	int pid_file;
	char pid_string[PID_LENGTH+1];
	
	
	char status_path[PATH_LENGTH];
	int status_file;
	char buffer[BUFFER_LENGTH+1];

	memset(pid_string,'\0',PID_LENGTH+1);
	memset(status_path,'\0',PATH_LENGTH+1);
	memset(buffer,'\0',BUFFER_LENGTH+1);

	if (access(PID_FILE_PATH,R_OK)==0)
	{
		pid_file=open(PID_FILE_PATH,O_RDONLY);

		read(pid_file,pid_string,PID_LENGTH);

		sprintf(status_path,"%s/%s/%s","/proc",pid_string,"status");

		if (access(status_path,R_OK)==0)
		{
			status_file=open(status_path,O_RDONLY);
			
			read(status_file,buffer,BUFFER_LENGTH);

			if (strstr(buffer,PROG_NAME)!=NULL)
			{
				fprintf(stderr,"%s","njubras-daemon has been started!\n");

				exit(1);
			}

			close(status_file);
		}

		close(pid_file);
	}
	
	daemon(1,0);

	unlink(PID_FILE_PATH);
	pid_file=open(PID_FILE_PATH,O_WRONLY|O_CREAT|O_EXCL,0644);

	daemon_pid=getpid();

	sprintf(pid_string,"%d",daemon_pid);
	write(pid_file,pid_string,strlen(pid_string));

	close(pid_file);
	

}

void handle_signal()
{
	signal(SIGTERM,daemon_exit);
	signal(SIGINT,daemon_exit);
	signal(SIGALRM,daemon_exit);
	signal(SIGHUP,daemon_exit);
}

void daemon_exit(int sigenum)
{
	unlink(PID_FILE_PATH);

	exit(-1);
}

void init_command_array(void)
{
	memset(command,'\0',COMMAND_LENGTH);

	admitted_command[0].command="bash bras_control.sh start";
	admitted_command[1].command="bash bras_control.sh stop";
	admitted_command[2].command="bash routes_change.sh add";
	admitted_command[3].command="bash routes_change.sh delete";
	admitted_command[4].command="bash check_bras_connect.sh";
	admitted_command[5].command="bash bras_control.sh kill";

	admitted_command[0].command_len=strlen("bash bras_control.sh start");
	admitted_command[1].command_len=strlen("bash bras_control.sh stop");
	admitted_command[2].command_len=strlen("bash routes_change.sh add");
	admitted_command[3].command_len=strlen("bash routes_change.sh delete");
	admitted_command[4].command_len=strlen("bash check_bras_connect.sh");
	admitted_command[5].command_len=strlen("bash bras_control.sh kill");

}

void init_server(void)
{
	unlink(SOCKET_PAHT);

	server_sockfd=socket(AF_UNIX,SOCK_STREAM,0);

	server_addr.sun_family=AF_UNIX;
	strcpy(server_addr.sun_path,SOCKET_PAHT);
	server_addr_size=sizeof(server_addr);

	bind(server_sockfd,SOCKADDR(&server_addr),server_addr_size);
	chmod(SOCKET_PAHT,622);

	listen(server_sockfd,5);
}

void get_command()
{
	client_addr.sun_family=AF_UNIX;
	strcpy(client_addr.sun_path,SOCKET_PAHT);
	client_addr_size=sizeof(client_addr);

	client_sockfd=accept(server_sockfd,SOCKADDR(&client_addr),&client_addr_size);

	read(client_sockfd,command,COMMAND_LENGTH);
	
}

int is_command_admitted()
{
	int admitted=0;
	int i;

	for(i=0;i<COMMAND_NUM;i++)
	{
		admitted=admitted||(!strncmp(command,admitted_command[i].command,admitted_command[i].command_len-1));
		if (admitted)
			return admitted;		
	}

	return admitted;
}
